/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/sync.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../toolkit/display.h"
#include "../toolkit/prop.h"
#include "../toolkit/xqueue.h"
#include "../toolkit/xml.h"
#include "geom.h"
#include "frame.h"
#include "misc.h"
#include "wm.h"
#include "window.h"
#include "stacking.h"
#include "client.h"
#include "grab.h"
#include "focus_cycle.h"
#include "focus_cycle_indicator.h"
#include "debug.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "moveresize.h"
#include "screen.h"
#define FRAME_EVENTMASK (EnterWindowMask | LeaveWindowMask | ButtonPressMask | ButtonReleaseMask | SubstructureRedirectMask | FocusChangeMask)
#define ELEMENT_EVENTMASK (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask)
static Window createWindow(Window parent, Visual *visual, gulong mask, XSetWindowAttributes *attrib)
{
	return XCreateWindow(t_display, parent, 0, 0, 1, 1, 0, (visual ? 32 : DefaultDepth(t_display, DefaultScreen(t_display))), InputOutput,
		(visual ? visual : DefaultVisual(t_display, DefaultScreen(t_display))), mask, attrib);
}

static Visual *check_32bit_client(struct wm_client *c)
{
	XWindowAttributes wattrib;
	Status ret;
	if(DefaultDepth(t_display, DefaultScreen(t_display)) == 32)
		return NULL;
	ret = XGetWindowAttributes(t_display, c->window, &wattrib);
	g_assert(ret != BadDrawable);
	g_assert(ret != BadWindow);
	if(wattrib.depth == 32)
		return wattrib.visual;
	return NULL;
}

struct wm_frame *frame_new(struct wm_client *client)
{
	XSetWindowAttributes attrib;
	gulong mask;
	struct wm_frame *self;
	Visual *visual;
	self = g_slice_new0(struct wm_frame);
	self->client = client;
	visual = check_32bit_client(client);
	mask = 0;
	if(visual) {
		mask = CWColormap | CWBackPixel | CWBorderPixel;
		self->colormap = attrib.colormap = XCreateColormap(t_display, DefaultRootWindow(t_display), visual, AllocNone);
		attrib.background_pixel = BlackPixel(t_display, DefaultRootWindow(t_display));
		attrib.border_pixel = BlackPixel(t_display, DefaultRootWindow(t_display));
	}
	self->window = createWindow(DefaultRootWindow(t_display), visual, mask, &attrib);
	mask = 0;
	if(visual) {
		mask = CWColormap | CWBackPixel | CWBorderPixel;
		attrib.colormap = DefaultColormap(t_display, DefaultScreen(t_display));
	}
	mask |= CWEventMask;
	attrib.event_mask = ELEMENT_EVENTMASK;
	self->focused = FALSE;
	STRUT_SET(self->oldsize, -1, -1, -1, -1);
	return self;
}

void frame_free(struct wm_frame *self)
{
	XDestroyWindow(t_display, self->window);
	g_slice_free(struct wm_frame, self);
}

void frame_show(struct wm_frame *self)
{
	if(!self->visible) {
		self->visible = TRUE;
		grab_server(TRUE);
		XMapWindow(t_display, self->client->window);
		XMapWindow(t_display, self->window);
		grab_server(FALSE);
	}
}

void frame_hide(struct wm_frame *self)
{
	if(self->visible) {
		self->visible = FALSE;
		XUnmapWindow(t_display, self->window);
		XUnmapWindow(t_display, self->client->window);
		self->client->ignore_unmaps += 1;
	}
}

void frame_adjust_shape_kind(struct wm_frame *self, int kind)
{
	gint num;
	XRectangle xrect[2];
	gboolean shaped;
	shaped = (kind == ShapeBounding && self->client->shaped);
	shaped |= (kind == ShapeInput && self->client->shaped_input);
	if(!shaped) {
		XShapeCombineMask(t_display, self->window, kind, self->size.left, self->size.top, None, ShapeSet);
	} else {
		XShapeCombineShape(t_display, self->window, kind, self->size.left, self->size.top, self->client->window, kind,
			ShapeSet);
		num = 0;
		XShapeCombineRectangles(t_display, self->window, ShapeBounding, 0, 0, xrect, num, ShapeUnion,
			Unsorted);
	}
}

void frame_adjust_shape(struct wm_frame *self)
{
	frame_adjust_shape_kind(self, ShapeBounding);
	frame_adjust_shape_kind(self, ShapeInput);
}

void frame_adjust_area(struct wm_frame *self, gboolean moved, gboolean resized, gboolean fake)
{
	if(resized) {
		self->max_horz = self->client->max_horz;
		self->max_vert = self->client->max_vert;
		if(self->max_horz) {//XXX!!!!!
			self->width = self->client->area.width;
		} else
			self->width = self->client->area.width ;
		STRUT_SET(self->size, 0, 0, 0, 0);
	}
	RECT_SET_SIZE(self->area, self->client->area.width + self->size.left + self->size.right,
					self->client->area.height + self->size.top + self->size.bottom);
	if((moved || resized) && !fake) {
		self->area.x = self->client->area.x;
		self->area.y = self->client->area.y;
	}
	if(!fake) {
		XMoveResizeWindow(t_display, self->window, self->area.x, self->area.y, self->area.width, self->area.height);
		XMoveWindow(t_display, self->client->window, self->size.left, self->size.top);
		if(resized) {
			frame_adjust_shape(self);
		}
		if(!STRUT_EQUAL(self->size, self->oldsize)) {
			gulong vals[4];
			vals[0] = self->size.left;
			vals[1] = self->size.right;
			vals[2] = self->size.top;
			vals[3] = self->size.bottom;
			self->oldsize = self->size;
		}
		if(focus_cycle_target == self->client)
			focus_cycle_update_indicator(self->client);
	}
}

void frame_adjust_focus(struct wm_frame *self, gboolean hilite)
{
	wm_debug_type(WM_DEBUG_FOCUS, "Frame for 0x%x has focus: %d", self->client->window, hilite);
	self->focused = hilite;
	XFlush(t_display);
}

void frame_grab_client(struct wm_frame *self)
{
	XReparentWindow(t_display, self->client->window, self->window, 0, 0);
	if(wm_state() == WM_STATE_STARTING)
		++self->client->ignore_unmaps;
	XSelectInput(t_display, self->window, FRAME_EVENTMASK);
	window_add(&self->window, CLIENT_AS_WINDOW(self->client));
}

static gboolean find_reparent(XEvent *e, gpointer data)
{
	const struct wm_frame *self = data;
	return e->type == ReparentNotify && e->xreparent.window == self->client->window && e->xreparent.parent != self->window;
}

void frame_release_client(struct wm_frame *self)
{
	if(!xqueue_exists_local(find_reparent, self)) {
		XReparentWindow(t_display, self->client->window, DefaultRootWindow(t_display), self->client->area.x,
			self->client->area.y);
	}
	window_remove(self->window);
}

gboolean frame_next_context_from_string(gchar *names, enum wm_frame_context *cx)
{
	gchar *p, *n;
	if(!*names)
		return FALSE;
	for(p = names; *p; p = g_utf8_next_char(p)) {
		const gunichar c = g_utf8_get_char(p);
		if(g_unichar_isspace(c))
			break;
	}
	if(p == names) {
		n = g_utf8_next_char(names);
		if(!frame_next_context_from_string(n, cx))
			return FALSE;
	} else {
		n = p;
		if(*p) {
			while(n < g_utf8_next_char(p))
				*(n++) = '\0';
		}
		*cx = frame_context_from_string(names);
		for(; *n; n = g_utf8_next_char(n)) {
			const gunichar c = g_utf8_get_char(n);
			if(!g_unichar_isspace(c))
				break;
		}
	}
	for(p = names; *n; ++p, ++n)
		*p = *n;
	*p = *n;
	return TRUE;
}

enum wm_frame_context frame_context_from_string(const gchar *name)
{
	if(!g_ascii_strcasecmp("Desktop", name))
		return WM_FRAME_CONTEXT_DESKTOP;
	else if(!g_ascii_strcasecmp("Root", name))
		return WM_FRAME_CONTEXT_ROOT;
	else if(!g_ascii_strcasecmp("Client", name))
		return WM_FRAME_CONTEXT_CLIENT;
	else if(!g_ascii_strcasecmp("Frame", name))
		return WM_FRAME_CONTEXT_FRAME;
	else if(!g_ascii_strcasecmp("MoveResize", name))
		return WM_FRAME_CONTEXT_MOVE_RESIZE;
	return WM_FRAME_CONTEXT_NONE;
}

enum wm_frame_context frame_context(struct wm_client *client, Window win, gint x, gint y)
{
	struct wm_frame *self;
	(void)x;
	(void)y;
	if(moveresize_in_progress)
		return WM_FRAME_CONTEXT_MOVE_RESIZE;
	if(win == DefaultRootWindow(t_display))
		return WM_FRAME_CONTEXT_ROOT;
	if(client == NULL)
		return WM_FRAME_CONTEXT_NONE;
	if(win == client->window) {
		if(client->type == WM_CLIENT_TYPE_DESKTOP)
			return WM_FRAME_CONTEXT_DESKTOP;
		return WM_FRAME_CONTEXT_CLIENT;
	}
	self = client->frame;
	if(win == self->window)
		return WM_FRAME_CONTEXT_FRAME;
	return WM_FRAME_CONTEXT_NONE;
}

void frame_rect_to_frame(struct wm_frame *self, struct wm_rect *r)
{
	r->width += self->size.left + self->size.right;
	r->height += self->size.top + self->size.bottom;
}
